﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CivLib.CivSpatial
{
  /// <summary>
  /// Abstract class that represents a generic geometry that lies in a plane
  /// </summary>
  public abstract class PlanarObject : GeometryObject, CivHyd.IHydraulicXsec
  {
    /// <summary>
    /// Array of the dimensions
    /// </summary>
    protected double[] dim;

    /// <summary>
    /// The array of dimensions
    /// </summary>
    public double[] Dim
    {
      get { return dim; }
      set { dim = value; }
    }

    /// <summary>
    /// Indexer for accessing dimensions
    /// </summary>
    /// <param name="i">index of the dimension</param>
    /// <returns>value of the dimension</returns>
    public double this[int i]
    {
      get { return dim[i]; }
      set { dim[i] = value; }
    }

    /// <summary>
    /// The number of dimensions
    /// </summary>
    public int DimCount { get { return dim.Length; } }

    /// <summary>
    /// Constructor for a PlanarObject
    /// </summary>
    /// <param name="a_name">name of the object</param>
    /// <param name="a_dimensions">number of dimensions of the object</param>
    /// <param name="a_numberOfPoints">number of points used to define the object</param>
    public PlanarObject(string a_name, int a_dimensions = 1, int a_numberOfPoints = 1) :
      base(a_name, a_numberOfPoints)
    {
      dim = new double[a_dimensions]; 
    }

    /// <summary>
    /// Method that returns the area of the planar object
    /// </summary>
    /// <returns>Area of planar object</returns>
    abstract public double Area();

    /// <summary>
    /// Method that returns the area of the planar object
    /// </summary>
    /// <returns>Area of planar object</returns>
    abstract public double Perimeter();

    /// <summary>
    /// Method to get the area at a specified height from the base, for rectangles and 
    /// circles this is fairly simple.
    /// </summary>
    /// <param name="heightFromBase">Height from the base of the object</param>
    /// <returns>Area contained in the height</returns>
    abstract public double PartialArea(double heightFromBase);

    /// <summary>
    /// Method to get the perimeter at a specified height from the base, for rectangles and 
    /// circles this is fairly simple.
    /// </summary>
    /// <param name="heightFromBase">Height from the base of the object</param>
    /// <returns>Perimeter contained in the height</returns>
    abstract public double PartialPerimiter(double heightFromBase);

    /// <summary>
    /// Method to get the width at a specified height from the base.
    /// </summary>
    /// <param name="heightFromBase">Height from the base of the object</param>
    /// <returns>Width at the the height</returns>
    abstract public double TopWidth(double heightFromBase);

    /// <summary>
    /// Offsets the abject by a specified amount.
    /// </summary>
    /// <param name="a_amount">Amount of offset, 
    /// - for deflation + for inflation</param>
    abstract public void Offset(double a_amount);
}



  /// <summary>
  /// Class that represents a basic circle, a derived PlanarObject
  /// </summary>
  public class Circle : PlanarObject
  {
    /// <summary>Diameter of the circle</summary>
    public double Diameter
    {
      get {return dim[0];}
      set {dim [0] = value;}
    }
    /// <summary>Radius of the circle</summary>
    public double Radius
    {
      get { return Dim[0] / 2; }
      set { Dim[0] = value * 2; }
    }

    /// <summary>
    /// Constructor for a Circle
    /// </summary>
    /// <param name="a_diameter">Diameter of the circle</param>
    public Circle(double a_diameter) :
      base("Generic Circle", 1, 1)
    {
      Diameter = a_diameter;
    }

    /// <summary>
    /// Area of the object
    /// </summary>
    /// <returns>the total area</returns>
    public override double Area()
    {
      return Math.PI * Radius * Radius;
    }

    /// <summary>
    /// Perimeter of the object
    /// </summary>
    /// <returns>the total perimeter</returns>
    public override double Perimeter()
    {
      return Math.PI * Diameter;
    }

    double Theta(double heightFromBase)
    {
      return 2 * Math.Acos(1 - 2 * (heightFromBase / Diameter));
    }

    /// <summary>
    /// Area contained in a given height from the base
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>agrea in given height</returns>
    public override double PartialArea(double heightFromBase)
    {
      double theta = Theta(heightFromBase);
      return 0.125d*(theta - Math.Sin(theta))*Diameter*Diameter;
    }

    /// <summary>
    /// Perimeter contained in a given height from the base
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>preimeter in given height</returns>
    public override double PartialPerimiter(double heightFromBase)
    {
      return 0.5d * Theta(heightFromBase) * Diameter;
    }

    /// <summary>
    /// The width at a given height
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>the width at a given height</returns>
    public override double TopWidth(double heightFromBase)
    {
      return 2 * Math.Sqrt(heightFromBase * (Diameter - heightFromBase));
    }

    /// <summary>
    /// Offsets the abject by a specified amount.
    /// </summary>
    /// <param name="a_amount">Amount of offset, 
    /// - for deflation + for inflation</param>
    public override void Offset(double a_amount)
    {
      Diameter += a_amount;
      if (Diameter < 0) Diameter = 0;
    }
  }

  /// <summary>
  /// Class that represents a basic rectangle
  /// </summary>
  public class Rectangle : PlanarObject
  {
    /// <summary>
    /// The width of the base
    /// </summary>
    public double Width 
    {
      get {return Dim[0];}
      set {dim[0] = value;}
    }

    /// <summary>
    /// The height of the rectangle
    /// </summary>
    public double Height
    {
      get {return Dim[1];}
      set {dim[1] = value;}
    }

    /// <summary>
    /// Constructor for rectangle
    /// </summary>
    /// <param name="a_width">width of rectangle</param>
    /// <param name="a_height">height of rectangle</param>
    public Rectangle(double a_width, double a_height):
      base("Generic Rectangle", 2, 4)
    {
      dim[0] = a_width;
      dim[1] = a_height;
    }

    /// <summary>
    /// Area of the object
    /// </summary>
    /// <returns>the total area</returns>
    public override double Area()
    {
      return Width * Height;
    }

    /// <summary>
    /// Perimeter of the object
    /// </summary>
    /// <returns>the total perimeter</returns>
    public override double Perimeter()
    {
      return 2 * Width + 2 * Height;
    }

    /// <summary>
    /// Area contained in a given height from the base
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>agrea in given height</returns>
    public override double PartialArea(double heightFromBase)
    {
      return heightFromBase > Height? Area() : (Width * heightFromBase);
    }

    /// <summary>
    /// Perimeter contained in a given height from the base
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>preimeter in given height</returns>
    public override double PartialPerimiter(double heightFromBase)
    {
      return Width + 2 * heightFromBase;
    }

    /// <summary>
    /// The width at a given height
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>the width at a given height</returns>
    public override double TopWidth(double heightFromBase)
    {
      return Width;
    }

    /// <summary>
    /// Offsets rectangle by specified amount
    /// </summary>
    /// <param name="a_amount">Offset amount, 
    /// - for deflation + for inflation</param>
    public override void Offset(double a_amount)
    {
      Width += a_amount;
      Height += a_amount;
      if (Width < 0) Width = 0;
      if (Height < 0) Height = 0;
    }
  }

  /// <summary>
  /// Class that represents a basic square
  /// </summary>
  public class Square : PlanarObject
  {
    /// <summary>Length of a side</summary>
    public double Side
    {
      get { return dim[0]; }
      set { dim[0] = value; }
    }

    Square(double a_side):
      base("Generic Square", 1, 4)
    {
      Side = a_side;
    }

    /// <summary>
    /// Area of the object
    /// </summary>
    /// <returns>the total area</returns>
    public override double Area()
    {
      return Side * Side;
    }

    /// <summary>
    /// Perimiter of the square
    /// </summary>
    /// <returns>the perimeter</returns>
    public override double Perimeter()
    {
      return 4 * Side;
    }

    /// <summary>
    /// Area contained in a given height from the base
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>agrea in given height</returns>
    public override double PartialArea(double heightFromBase)
    {
      return heightFromBase > Side ? Area() : (heightFromBase * Side);
    }

    /// <summary>
    /// Perimeter contained in a given height from the base
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>preimeter in given height</returns>
    public override double PartialPerimiter(double heightFromBase)
    {
      return heightFromBase > Side ? Perimeter() : 
        (Side + 2 * heightFromBase);
    }

    /// <summary>
    /// The width at a given height
    /// </summary>
    /// <param name="heightFromBase">height from the base</param>
    /// <returns>the width at a given height</returns>
    public override double TopWidth(double heightFromBase)
    {
      return Side;
    }

    /// <summary>
    /// Offsets square by specified amount
    /// </summary>
    /// <param name="a_amount">Offset amount, 
    /// - for deflation + for inflation</param>
    public override void Offset(double a_amount)
    {
      Side += a_amount;
      if (Side < 0) Side = 0;
    }
  }
}
